#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
#  Copyright (C) 2011 - Intrinsec - http://www.intrinsec.com
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Authors:
# Cyrille Barthelemy - cyrille.barthelemy@intrinsec.com
#

import os
import sys
import getopt
from urlparse import urlparse
import iptools
import time # use for date manipulation (filename generation etc.)
import re # use for reg exp
import socket # use for socket access
import httplib # use for HTTP bot

debug=True
vernum="0.1"
verbose=False
foundVulnerable=True

def usage():
    """
    This function prints the posible options of this program.
    """
    print "+----------------------------------------------------------------------+"
    print "| Intrinsec-DOS-Apache-Range-Header-Tester - version "+ vernum +"               |"
    print "|                                                                      |"
    print "| This program is used to test :                                       |"
    print "|  * CVE-2011-3192 : Range header DoS vulnerability in Apache 1.3 and 2|"
    print "|                                                                      |"
    print "| Authors:                                                             |"
    print "|  Cyrille Barthelemy - cyrille.barthelemy@intrinsec.com               |"
    print "| Intrinsec - www.intrinsec.com                                        |"
    print "|                                                                      |"
    print "+----------------------------------------------------------------------+"
    print
    print "\nUsage: %s <options>" % sys.argv[0]
    print "Options:"
    print "  -h, --help                           Show this help message and exit."
    print "  -r, --range                          Network perimeter (IP, CIDR) to test."
    print "  -H, --host                           Host to test."
    print "  -p, --ports                          Ports to test (443 will be tested on HTTPS) for each IP. Default 80."
    print "  -v, --verbose                        Verbose."
    print " "
    print "Example: "+sys.argv[0] +" -v -H wwww.mylan.local -p 80,443"
    print
    sys.exit(1)


def checkVulnerable(target, type, ports_list):
    global foundVulnerable

    if type == "host":
        headers = { "Host" : target, "Range" : "bytes=0-", "Accept-Encoding" : "gzip" , "Connection" : "close" }
    else:
        headers = { "Range" : "bytes=0-", "Accept-Encoding" : "gzip", "Connection" : "close" }
    ports = ports_list.split(',')

    for port in ports:
        if verbose:
            sys.stdout.write("Testing " + target + ":" + port)
        try:
            if port == "443":
                conn = httplib.HTTPSConnection(target,int(port))
            else:
                conn = httplib.HTTPConnection(target, int(port))
            if debug:
                httplib.HTTPConnection.debuglevel = 100
            conn.request("HEAD", "/", "", headers)
            response = conn.getresponse()
            if debug:
                sys.stdout.write(" response %s " % response.reason)
            if re.search("Partial", response.reason): # response.status == 206 would also work - just use the same test than the tool published 
                print " : Host %s seems to be vulnerable <<<===" % target
                foundVulnerable=True
            else:
                if response.status == 403:
                    sys.stdout.write(" (host should be tested with hostname for vhosts)")
                if verbose:
                    sys.stdout.write(" : Host seems to be not vulnerable")
                print ""
        except socket.error, error:
            if verbose:
                print " : [!!] Network error: %s" % error

def main():
    arg_Host=""
    arg_IPperimeter=""
    arg_Ports=""
    target_perimeter=""
    global foundVulnerable
    foundVulnerable=False

    default_timeout = 3
    socket.setdefaulttimeout(default_timeout)

    print "Intrinsec - Apache Range header DoS vulnerability testing - " + vernum 
    print "----------------------------------------------------------------"
    print ""

    try:
        global debug
        global verbose

        options,remain = getopt.getopt(sys.argv[1:], "hr:H:p:v", ["help","range=", "host=", "ports=", "verbose"])

    except getopt.GetoptError:  usage()

    if (len(sys.argv) <= 1):
      usage()

    for opt, arg in options:
        if opt in ('-r', '--range'):
            arg_IPperimeter = arg
        elif opt in ('-H', '--host'):
            arg_Host = arg
        elif opt in ('-v', '--verbose'):
            verbose = True
        elif opt in ('-p', '--ports'):
            arg_Ports = arg
        elif opt in ('-h', '--help'):
            usage()

    if (arg_Host == "") and (arg_IPperimeter == ""):
        usage()

    if (arg_IPperimeter != ""):
        if (not(iptools.validate_ip(arg_IPperimeter)) and not(iptools.validate_cidr(arg_IPperimeter))): 
             print "[**] Invalid network description (-r) : quad dot IP or CIDR notation only.\n\n"
             usage()
        target_perimeter = iptools.IpRangeList(arg_IPperimeter)

    if (arg_Ports == ""):
        arg_Ports = "80"

    if (arg_Host != ""):
        if (re.search("/", arg_Host)):
            print "[**] Please provide hostname, no URL.\n\n"
            usage()
        checkVulnerable(arg_Host, "host", arg_Ports)
    else:
        for ip in target_perimeter:
            checkVulnerable(ip, "ip", arg_Ports)

    if foundVulnerable:
        print "\n"
        print "==> Watch for patch to be provided by Apache (CVE-2011-3192)"
        print "==> Immediate mitigation can be found at : http://marc.info/?l=apache-httpd-dev&m=131418828705324&w=2"
    else:
        print "No vulnerable host found.\n"

if __name__ == "__main__":
    main()
